#include "ai.h"
#include <vector>
#include "bitboard.h"
#include <iostream>

using namespace std;

// ---------
// inicializace objektu
AI::AI(int maxSearchDepth) {
	this->mg = new MoveGenerator();
	moves = new Moves[maxSearchDepth + 1];
	this->maxDepth = maxSearchDepth;
}

// ---------
// destruktor
AI::~AI() {
	delete mg;
	delete moves;
}

// ----------
// Funkce ktera pro zadaneho hrace a zadany stav hraci plochy najde "nejlepsi" tah
Move AI::ChooseMove(CheckerColor player, Board board) {
	this->moves[0].Clear();
	this->mg->Generate(player, board, &this->moves[0]);

	// samotne prohledavani stavoveho prostoru algoritmem MINMAX
	this->minmax(0, player, board, -100000, 100000);
	
	int bestIndex = 0;
	
	// mezi ohodnocenymi tahy najde ten nejlepsi
	for(int i = 0; i < this->moves[0].MovesCount; i++) {
		if((player == WHITE) && (this->moves[0].MovesList[i].FitnessTotal > this->moves[0].MovesList[bestIndex].FitnessTotal) ||
			(player == BLACK) && (this->moves[0].MovesList[i].FitnessTotal < this->moves[0].MovesList[bestIndex].FitnessTotal)) {

			bestIndex = i;
		}
	}
	// provede tah a ohodnoti pozici, ktera po tahu vznikla
	Board afterMove = this->moves[0].MovesList[bestIndex].BeforeMove;
	afterMove.DoMoveAndCheck(this->moves[0].MovesList[bestIndex], &this->moves[0]);
	this->EvaluateMove(afterMove, &this->moves[0], bestIndex);

	return this->moves[0].MovesList[bestIndex];
}

// ----------
// algoritmus prohledavani stavoveho porstoru MINMAX
int AI::minmax(int depth, CheckerColor player, Board board, int a, int b) {
  CheckerColor enemy;
	player == WHITE ? enemy = BLACK : enemy = WHITE;

	int winner = -1;

	int best;
	player == WHITE ? best = -1000000 : best = 1000000;

	for(int i = 0; i < this->moves[depth].MovesCount; i++) {
		// provede tah
		Board afterMove = this->moves[depth].MovesList[i].BeforeMove;
		afterMove.DoMoveAndCheck(this->moves[depth].MovesList[i], &this->moves[depth]);

		if(depth == this->maxDepth) {
			this->EvaluateMove(afterMove, &this->moves[depth], i);
		}
		else {
			// vygeneruje tahy na dalsi urovni
			this->moves[depth + 1].Clear();
			this->mg->Generate(enemy, afterMove, &this->moves[depth + 1]);

			// zkontroluje, jestli po odehrani tahu nenastal konec hry
			if(afterMove.IsEndGame(&winner, enemy, &this->moves[depth + 1])) {
				this->EvaluateMove(afterMove, &this->moves[depth], i);
			}
			// rekurzivne pokracuje prohledavani algoritmem MINMAX
			else {
				this->moves[depth].MovesList[i].FitnessTotal = minmax(depth+1, enemy, afterMove, a, b);
			}
		}

		// ALFA BETA prorezavani
		if(player == WHITE) {
			if(this->moves[depth].MovesList[i].FitnessTotal >= b)
				return b;
			if(this->moves[depth].MovesList[i].FitnessTotal > a)
				a = this->moves[depth].MovesList[i].FitnessTotal;
		}

		if(player == BLACK) {
			if(this->moves[depth].MovesList[i].FitnessTotal <= a)
				return a;
			if(this->moves[depth].MovesList[i].FitnessTotal < b)
				b = this->moves[depth].MovesList[i].FitnessTotal;
		}
	}

	if (player == WHITE) 
		return a;
	else
		return b;
}

// ----------
// Funkce ktera ohodnoti zadany stav hraci plochy
// moves - vsechny tahy ze stejne urvne jako tah, ktery do zadaneho stavu vedl
// i - index tahu, ktery do zadaneho stavu vedl, v poli moves
int AI::EvaluateMove(Board board, Moves* moves, int i) {
		Moves movesCaptures;

		CheckerColor player = moves->MovesList[i].Player;
		CheckerColor enemy;
		(player == WHITE) ? enemy = BLACK : enemy = WHITE;

		int result = 0;
		int winner = -1;

		// ----------
		// ohodnoceni poctu figur
		
		// normalni bile kameny
		moves->MovesList[i].FitnessComponents[0] = BitboardUtils::BitsCount(board.Color[WHITE] & board.NormalCheckers);
		result += moves->MovesList[i].FitnessComponents[0] * this->weights[0];

		// normalni cerne kameny
		moves->MovesList[i].FitnessComponents[1] = BitboardUtils::BitsCount(board.Color[BLACK] & board.NormalCheckers);
		result += moves->MovesList[i].FitnessComponents[1] * this->weights[1];

		// bile damy
		moves->MovesList[i].FitnessComponents[2] = BitboardUtils::BitsCount(board.Color[WHITE] & board.Queens);
		result += moves->MovesList[i].FitnessComponents[2] * this->weights[2];

		// cerne damy
		moves->MovesList[i].FitnessComponents[3] = BitboardUtils::BitsCount(board.Color[BLACK] & board.Queens);
		result += moves->MovesList[i].FitnessComponents[3] * this->weights[3];

		// -----------
		// ohodnoceni poctu ohrozovanych figur

		// pocita kolik kamenu soupere ohrozuje bily hrac
		mg->Generate(WHITE, board, &movesCaptures);

		moves->MovesList[i].FitnessComponents[4] = MaxCaptures(&movesCaptures);
		result += moves->MovesList[i].FitnessComponents[4] * this->weights[4];

		// pocita koli kamenu soupere ohrozuje cerny hrac
		movesCaptures.Clear();
		mg->Generate(BLACK, board, &movesCaptures);

		moves->MovesList[i].FitnessComponents[5] = MaxCaptures(&movesCaptures);
		result += moves->MovesList[i].FitnessComponents[5] * this->weights[5];

		// ohodnoce konce hry
		if(board.IsEndGame(&winner, enemy)) {
			result = this->EvaluateEndGame(winner);
		}

		moves->MovesList[i].FitnessTotal = result;
		return result;
}

// Spocita kolik kamenu soupere hrac celkove ohrozuje
// moves - tahy, ktere ma hrac na vyber
int AI::MaxCaptures(Moves* aMoves) {
	vector<int> captured;

	for(int i = 0; i < aMoves->MovesCount; i++) {
		for(int c = 0; c < aMoves->MovesList[i].CapturedCount; c++) {
			// zjistujeme jestli dana figura jiz neni ve vektoru captured,
			// delame to proto, abychom nektere vyhozene kameny nepocitali vicekrat
			bool insert = true;
			for(int j = 0; j < captured.size(); j++) {
				if(captured[j] == aMoves->MovesList[i].Captured[c]) {
					insert = false;
					break;
				}
			}
			if(insert)
				// pokud dana figura jeste neni ve vektoru captured vlozime ji tam
				// 
				captured.push_back( aMoves->MovesList[i].Captured[c]);
		}
	}

	return captured.size();
}

// prirazi ohodnoceni na zaklade toho kdo vyhral
int AI::EvaluateEndGame(int winner) {
	switch(winner) {
		case -1:		return 0;
		case WHITE:	return 10000;
		case BLACK: return -10000;
	}
}